/**
 * 
 */
package co.fitstart.api.service.impl;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.demo2do.core.BusinessException;
import com.demo2do.core.service.impl.GenericServiceImpl;
import com.demo2do.core.support.Result;
import com.demo2do.core.utils.DateFormatUtils;
import com.demo2do.core.utils.DateUtils;
import com.demo2do.core.utils.JsonUtils;
import com.demo2do.core.web.support.HttpResult;

import co.fitstart.api.dao.CoachDaoSupport;
import co.fitstart.api.dao.CouponDaoSupport;
import co.fitstart.api.dao.PrincipalDaoSupport;
import co.fitstart.api.dao.ProductDaoSupport;
import co.fitstart.api.dao.StudentDaoSupport;
import co.fitstart.api.handler.SmsSender;
import co.fitstart.api.handler.WxcClientOrderHandler;
import co.fitstart.api.service.OrderService;
import co.fitstart.entity.Constant;
import co.fitstart.entity.coach.CoachSigninRecord;
import co.fitstart.entity.coach.CoachSigninStatistic;
import co.fitstart.entity.coach.PrincipalReportRelation;
import co.fitstart.entity.coupon.Coupon;
import co.fitstart.entity.coupon.CouponBatch;
import co.fitstart.entity.course.Course;
import co.fitstart.entity.course.CourseType;
import co.fitstart.entity.course.round.CourseRound;
import co.fitstart.entity.course.schedule.CourseSchedule;
import co.fitstart.entity.credit.Credit;
import co.fitstart.entity.credit.CreditTransaction;
import co.fitstart.entity.finance.PaidType;
import co.fitstart.entity.finance.Receipt;
import co.fitstart.entity.finance.ReceiptType;
import co.fitstart.entity.finance.Receivables;
import co.fitstart.entity.order.Order;
import co.fitstart.entity.order.OrderAttribute;
import co.fitstart.entity.order.OrderInvestigateRecord;
import co.fitstart.entity.order.PaymentMethod;
import co.fitstart.entity.order.PaymentRequest;
import co.fitstart.entity.product.Product;
import co.fitstart.entity.product.stat.UserProductBehavior;
import co.fitstart.entity.site.Site;
import co.fitstart.entity.taste.TasteSignUp;
import co.fitstart.entity.taste.TasteStat;
import co.fitstart.entity.user.Student;
import co.fitstart.entity.user.User;
import co.fitstart.entity.user.agent.Agent;
import co.fitstart.entity.user.agent.AgentDiscount;
import co.fitstart.entity.user.agent.AgentRecord;
import co.fitstart.entity.user.agent.DiscountType;
import co.fitstart.entity.user.agent.OfflineAgent;
import co.fitstart.entity.user.agent.RecordType;
import co.fitstart.entity.user.distributor.DistributionRecord;
import co.fitstart.entity.user.distributor.DistributionRecordType;
import co.fitstart.entity.user.distributor.Distributor;
import co.fitstart.entity.user.distributor.DistributorUserRelation;

/**
 * @author downpour
 *
 */
@Service("orderService")
public class OrderServiceImpl extends GenericServiceImpl<Order> implements OrderService {
    
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    
    private static final int COUPON_AMOUNT = 8;
    @Autowired
    private ProductDaoSupport productDaoSupport;
    
    @Autowired
    private StudentDaoSupport studentDaoSupport;
    
    @Autowired
    private CouponDaoSupport couponDaoSupport;
    
    @Autowired
    private PrincipalDaoSupport principalDaoSupport;
    
    @Autowired
    private SmsSender sender;
    
    @Autowired
    private CoachDaoSupport coachDaoSupport;
    
    @Autowired
    private WxcClientOrderHandler wxcClientOrderHandler;
    
    /**
     * 
     * @param orderNo
     * @return
     */
    private Order loadOrder(String orderNo) {
        String sentence = "FROM Order order WHERE order.disabled = false AND order.code = :code";
        return genericDaoSupport.searchForObject(sentence, "code", orderNo, Order.class);
    }
    
    /**
     * 
     * @param orderNo
     * @param customerId
     */
    private Order checkOrder(String orderNo) {
        
        Order order = this.loadOrder(orderNo);
        
        if (order == null) {
            throw new BusinessException("订单" + orderNo + "不存在");
        }
        
        return order;
    }
    
    /**
     * 
     * @param user
     * @param order
     */
    private void editUserCredit(User user, Order order) {
        
        Long userId = user.getId();
        Credit credit = studentDaoSupport.getUserCredit(userId);
        
        if(credit == null) {
            credit = new Credit(user);
            genericDaoSupport.save(credit);
        }
        
        // update credit's total & current
        int creditValue = new BigDecimal(order.getPrice()).divide(new BigDecimal(100)).intValue();
        genericDaoSupport.update(credit.increaseCredit(creditValue));
        
        // save new credit transaction
        CreditTransaction creditTransaction = new CreditTransaction(credit);
        genericDaoSupport.save(creditTransaction.order(order));
    }
    
    /**
     * 
     * @param order
     */
    private void updateProductStatistics(String openid, Product product, int amount) {
        
        // 1. update product salesamount
        genericDaoSupport.update(product.increase(amount));
        
        // 2. update product user behavior
        UserProductBehavior userProductBehavior = productDaoSupport.loadUserProductBehavior(product, openid);
        if(userProductBehavior != null) {
            genericDaoSupport.update(userProductBehavior.order());
        }
        
    }
    
    /**
     * create student based on user and his order
     *  
     * @param user
     * @param order
     */
    private Student createStudent(User user, Order order) {

        Student student = studentDaoSupport.getStudentByUserId(user.getId());

        if(student != null ) {

            student = this.editOfflineCode(student, order);
            student.increaseStat(order);
            genericDaoSupport.update(student.edit(order.getCourse()));
            
        } else {
           
            // create student first
            student = new Student(user, order.getCourse()); 
            student = this.editOfflineCode(student, order);
            genericDaoSupport.save(student.increaseStat(order));
            
            // update user's student status
            genericDaoSupport.update(user.student());
        }

        String contactPhone = order.attrValue("contactPhone");
        if(user.isSelf(contactPhone)) {
            // TODO add order alert here
        }
        
        return student;
        
    }
    
    /**
     * 
     * @param student
     * @param order
     * @return
     */
    private Student editOfflineCode(Student student, Order order) {
        if(order.getCourse().couldSelectCode() && StringUtils.isEmpty(student.getOfflineCode())) {
            student.editOfflineCode();
        }
        return student;
    }
    
    /**
     * 
     * @param code
     * @param order
     * @param student
     */
    private void createOfflineAgent(Order order, Student student) {
        
            // 1. create courseRound first
            Course course = order.getCourse();
            Site site = order.getSite();
            
            // 1.1. create basic fields
            CourseRound courseRound = new CourseRound(student, order.getProduct());
            
            // 1.2. initialize course and code
            courseRound.initGivenRound(course, site);
            
            // 1.3. generate course round code and save
            genericDaoSupport.save(courseRound);
            logger.trace("createCourseRound - courseRound [{}] save courseAmount [{}]",courseRound.getId(), courseRound.getAttrs().get(Constant.COURSE_AMOUNT));
            
            // 2. create offlineAgent
            OfflineAgent offlineAgent = new OfflineAgent(student.getOfflineCode(), order, courseRound);
            genericDaoSupport.save(offlineAgent);

    }

    
    /**
     * create course round based on order
     * 
     * @param student
     * @param order
     * @return
     */
    private CourseRound createCourseRound(Student student, Order order) {
        
        Course course = order.getCourse();
        Site site = order.getSite();
        
        // 1. create basic fields
        CourseRound courseRound = new CourseRound(student, order);
        
        // 2. initialize course and code
        courseRound.initialize(course, site);
        
        // 3. generate course round code and save
        genericDaoSupport.save(courseRound);
        logger.trace("createCourseRound - courseRound [{}] save courseAmount [{}]",courseRound.getId(), courseRound.getAttrs().get(Constant.COURSE_AMOUNT));
        
        // 4. send message to manager
        CourseType courseType = courseRound.getCourseType();
        if(courseType.isIndividual()){
            sender.sendBuyCourseNotice(courseRound.getStudent().getName(), courseRound.getSite().getManager().getMobile(), courseRound.getProduct().getName());
        }
        
        return courseRound;
    }
    
    /**
     * 
     * @return
     */
    private String getEntryPerson(Long siteId) {
        List<PrincipalReportRelation> relations = principalDaoSupport.listSiteManager();
        for(PrincipalReportRelation relation : relations) {
            if(relation.getSite().getId() == siteId) {
                return relation.getManager().getName();
            }
        }
        return null;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.api.service.OrderService#getOrder(java.lang.String)
     */
    public Order getOrder(String orderNo) {
        return this.loadOrder(orderNo);
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.api.service.OrderService#callback(co.fitstart.entity.order.PaymentRequest)
     */
    public Result callback(PaymentRequest paymentRequest) {
        
        Result result = new Result();
        
        String orderNo = paymentRequest.getOrderNo();
        
        // 1. get order info by order no
        Order order = this.checkOrder(orderNo);
        
        if(order.getPaymentStatus().isPaid()) {
            result.fail().message("this order already paid, pelase do not call again");
            return result;
        }
        
        Product product = order.getProduct();
        User user = order.getUser();
        
        // add order paid price
        order.addAttribute(Constant.ORDER_PAID_PRICE, paymentRequest.getAmount());
        
        // 2. update order include --- status & payment status & record pay code & pay time & pay method
        order.paid(paymentRequest.getRequestNo(), DateUtils.parseDate(paymentRequest.getPayTime(), "yyyyMMddHHmmss"), paymentRequest.getChannel(), paymentRequest.getAmount());
        
        // 2.1 save new credit transactions & edit user's credit
        this.editUserCredit(user, order);
        
        // 2.2 update agent info
        if(order.getAgent() != null) {
            AgentDiscount agentDiscount = genericDaoSupport.searchForObject("FROM AgentDiscount WHERE disabled = false AND discountCode = :discountCode", "discountCode", order.getDiscountCode(), AgentDiscount.class);
            
            if(agentDiscount != null) {
                Agent agent = agentDiscount.getAgent();
                genericDaoSupport.update(agent.consume(order, agentDiscount));
                
                // save agentRecord
                AgentRecord agentRecord = new AgentRecord(agent, RecordType.EARNED, order.getCommission());
                genericDaoSupport.save(agentRecord);
            }
        }
        
        
        DistributorUserRelation relation = genericDaoSupport.searchForObject("FROM DistributorUserRelation WHERE user = :user AND consumed = false ORDER BY createTime ASC", "user", user, DistributorUserRelation.class);
        if(relation != null) {
            
            Distributor distributor = relation.getDistributor();
            int offer = new BigDecimal(distributor.getKickback())
                            .divide(new BigDecimal(100))
                            .multiply(new BigDecimal(order.getPrice()))
                            .intValue();
            
            // create new record
            DistributionRecord distributionRecord = new DistributionRecord(distributor, DistributionRecordType.EARNED, offer);
            genericDaoSupport.save(distributionRecord);
            
            genericDaoSupport.update(distributor.consume(offer));
            
            genericDaoSupport.update(relation.consume(order));
        }
        
        // 3. update product & user product behavior
        this.updateProductStatistics(user.getOpenid(), product, order.getAmount());
        
        // 4. if not other type products 
        if(!order.getOrderType().isOthers() && !order.isOnlineYear()) {
            
            // check whether the user is a student | create student
            Student student = this.createStudent(user, order);
            
            // increase student course stat course & create course round if student exit
            if(student != null) {
                
                // update order's student
                order.student(student);
                
                //create course round
                this.createCourseRound(student, order);
                
            }
            
            // add investigation record
            OrderInvestigateRecord orderInvestigateRecord = new OrderInvestigateRecord(order);
            genericDaoSupport.save(orderInvestigateRecord);
            
        } else if(order.getOrderType().isOthers()) {
            //send message
            sender.sendSaleProductNotice("13162193559", "周边订单");
            
        }
        
        // if paymentMethod is taobao update orderNumber
        String orderNumber = paymentRequest.getOrderNumber();
        String channel = paymentRequest.getChannel();
        if(StringUtils.isNoneEmpty(channel) && channel.startsWith(PaymentMethod.TAOBAO.toString())) {
            // 付款方式为淘宝时创建应收款单
            Receivables receivables = new Receivables(order, paymentRequest.getAmount());
            genericDaoSupport.save(receivables);
            
            if(StringUtils.isNoneEmpty(orderNumber)) {
                order.addAttribute(Constant.ONLINE_ORDER_NUMBER, orderNumber);
            }
        }
        
        // update order
        genericDaoSupport.update(order);
        
        // if this order pay offer create balance receipt
        if(order.isPayOffer()) {
            String entryPerson = this.getEntryPerson(order.getSite().getId());
            
            Receipt receipt = new Receipt(order, ReceiptType.BALANCE, PaidType.BALANCE, paymentRequest.getAmount(), entryPerson);
            genericDaoSupport.save(receipt);
        }
        
        if(order.getOrderType().isOnline()) {
            
            // if course type is online create agent for this user
            Agent existAgent = genericDaoSupport.searchForObject("FROM Agent WHERE disabled = false AND user = :user", "user", user, Agent.class);
            if(existAgent == null) {
                
                // save agent first;
                Agent agent = new Agent(user);
                Long agentId = (Long) genericDaoSupport.save(agent);
                agent.setId(agentId);
                
                // save agentDiscount
                AgentDiscount agentdiscount = new AgentDiscount(agent, DiscountType.PAYMENT, 19900, 5000);
                genericDaoSupport.save(agentdiscount);
            }
            
        }
        
        // 5. set coupon as used & update coupon batch & coupon template stat
        if(MapUtils.isNotEmpty(order.getAttrs()) && order.getAttrs().get("couponId") != null) {
            Long couponId = Long.parseLong(order.getAttrs().get("couponId").getValue());
            Coupon coupon = genericDaoSupport.load(Coupon.class, couponId);
            couponDaoSupport.userCoupon(coupon, order.getProduct().getCurrentOffer());
        }
        
        // 6. if order is individual or year create offlineAgent and courseRound
        if(StringUtils.isNoneBlank(order.getDiscountCode()) && order.getCourse().couldSelectCode()) {
            String querySentence = "FROM Student WHERE disabled = false AND offlineCode = :code";
            Student student = genericDaoSupport.searchForObject(querySentence, "code", order.getDiscountCode(), Student.class);
            this.createOfflineAgent(order, student);
        }
        
        // 7. if order is online year, create 8 coupons
        if(order.isOnlineYear()) {
            CouponBatch couponBatch = product.getCouponBatch();
            genericDaoSupport.update(couponBatch.editClaimedAmount(COUPON_AMOUNT).editReleaseAmount(COUPON_AMOUNT));
            
            for(int i=0; i<COUPON_AMOUNT; i++) {
                Coupon coupon = new Coupon(couponBatch);
                coupon.generateCode();
                coupon.setOrder(order);
                genericDaoSupport.save(coupon.bindUser(user.getId()));
            }
        }
        
        // 8. if activity, add stat count
        if(order.getOrderType().isActivity()) {
            TasteSignUp taste = genericDaoSupport.searchForObject("FROM TasteSignUp WHERE order = :order", "order", order, TasteSignUp.class);
            if(taste != null) {
                String source = taste.getSource();
                if(source != null) {
                    Date statDay = DateUtils.parseDate(DateFormatUtils.format(order.getPayTime(), "yyyy-MM-dd"), "yyyy-MM-dd");
                    
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(order.getPayTime());
                    Integer statHour = calendar.get(Calendar.HOUR_OF_DAY);
                    
                    Map<String, Object> parameters = new HashMap<>();
                    parameters.put("source", source);
                    parameters.put("statDay", statDay);
                    parameters.put("statHour", statHour);
                    
                    TasteStat stat = genericDaoSupport.searchForObject("FROM TasteStat WHERE source = :source AND statDay = :statDay AND statHour = :statHour", parameters, TasteStat.class);
                    
                    if(stat == null) {
                        stat = new TasteStat(source, statDay, statHour);
                        genericDaoSupport.save(stat.addOrder());
                    } else {
                        genericDaoSupport.update(stat.addOrder());
                    }
                }
            }
        }
        
        // 9. 体验课计入课时
        if(order.getOrderType().isOnline() || (order.getOrderType().isIndividual() && !order.getCourse().isExperience()) || order.getOrderType().isYear()) {
            if(!user.isOrdered()) {
                genericDaoSupport.update(user.ordered());
                List<CourseSchedule> scheduleList = studentDaoSupport.listFinishedExperienceSchedule(order.getUser().getId());
                
                for(CourseSchedule courseSchedule : scheduleList) {
                    if(courseSchedule.getCoach() != null && courseSchedule.getOccurDate() != null && courseSchedule.getOccurHour() != null) {
                        CoachSigninRecord coachSigninRecord = coachDaoSupport.loadCoachSigninRecord(courseSchedule);
                        
                        if(coachSigninRecord != null) {
                            genericDaoSupport.update(coachSigninRecord.editIgnored());
                            
                            CoachSigninStatistic signinStatistic = coachDaoSupport.getCoachSigninStatistic(courseSchedule.getCoach(),
                                    DateFormatUtils.format(courseSchedule.getOccurDate(), "yyyy-MM"));
                            
                            if (signinStatistic == null) {
                                signinStatistic = new CoachSigninStatistic(courseSchedule);
                                genericDaoSupport.save(signinStatistic);
                            } else {
                                genericDaoSupport.update(signinStatistic.experienceHour(1.5));
                            }
                        }
                    }
                }
            }
        }
        
        // 10. 万象城数据实时上传商场系统
        if(order.getSite() != null && order.getSite().getId().equals(Constant.WAN_XIANG_CHENG_ID)) {
           HttpResult httpResult = wxcClientOrderHandler.create(order);
           logger.info("#WxcOrderUpload# upload order [{}], get result[{}]", order.getId(), JsonUtils.toJsonString(httpResult));
           if(!httpResult.isValid()) {
                throw  new BusinessException("upload order failed");
            } else if(httpResult.isValid()) {
               order.setUploadWxc(true);
               genericDaoSupport.update(order);
               
               // then add attr upload wxc time
               OrderAttribute orderAttribute = new OrderAttribute(order, "UPLOAD_WXC_TIME", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
               genericDaoSupport.save(orderAttribute);
           }
        }
        
        result.success().message("payment callback success");
        
        return result;
        
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.api.service.OrderService#callback(co.fitstart.entity.order.Order, java.lang.String, int, java.util.Date, co.fitstart.entity.order.PaymentMethod)
     */
    public void callback(Order order, String requestNo, int amount, Date payTime, PaymentMethod paymentMethod) {

        Product product = order.getProduct();
        User user = order.getUser();
        
        // 2. update order include --- status & payment status & record pay code & pay time & pay method
        order.paid(requestNo, payTime, paymentMethod.getAlias(), amount);
        
        // 3. update product & user product behavior
        this.updateProductStatistics(user.getOpenid(), product, order.getAmount());
        
        // 4. if not other type products 
        if(!order.getOrderType().isOthers()) {
            
            // check whether the user is a student | create student
            Student student = this.createStudent(user, order);
            
            // create course round if student exit
            if(student != null) {
                order.student(student);
                
                this.createCourseRound(student, order);
            }
            
            // update order
            genericDaoSupport.update(order);
            
            // add investigation record
            OrderInvestigateRecord orderInvestigateRecord = new OrderInvestigateRecord(order);
            genericDaoSupport.save(orderInvestigateRecord);
            
        }
        
        
        // 5. set coupon as used & update coupon batch & coupon template stat
        if(MapUtils.isNotEmpty(order.getAttrs()) && order.getAttrs().get("couponId") != null) {
            Long couponId = Long.parseLong(order.getAttrs().get("couponId").getValue());
            Coupon coupon = genericDaoSupport.load(Coupon.class, couponId);
            couponDaoSupport.userCoupon(coupon, order.getProduct().getCurrentOffer());
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.api.service.OrderService#initCreditData()
     */
    public void initCreditData() {
        
        List<Order> orders = studentDaoSupport.listAllPaidOrder();
        
        logger.info("#initCreditData() - start init credit data with orders ============= paid amount [{}]", orders.size());
        
        for(Order order : orders) {
            
            if(order.getUser() == null || order.getProduct() ==  null || order.getPrice() == 0) {
                logger.info("#initCreditData() - current order[{}] data is not validate ++++++++++++++++++++++++++++", order.getId());
            } else {
                
                User user = order.getUser();
                Long userId = user.getId();
                Credit credit = studentDaoSupport.getUserCredit(userId);
                
                // update credit's total & current
                int creditValue = new BigDecimal(order.getPrice()).divide(new BigDecimal(100)).intValue();
                genericDaoSupport.update(credit.increaseCredit(creditValue));
                
                // save new credit transaction
                CreditTransaction creditTransaction = new CreditTransaction(credit);
                genericDaoSupport.save(creditTransaction.order(order));
                
                logger.info("#initCreditData() - update current order[{}] user update success ---------------------------", order.getId());
            }
        }
    }
}
